using System;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Web.Security;
using log4net;

namespace Sedna.Core.Security
{
	/// <summary>
	/// A native NTLM authentication module that allows using a host groups or ActiveDirectory
	/// as Users and their roles mapping repository. The System (WebUI) roles are mapped directly 
	/// to the roles declared in Windows (ActiveDirectory). Since role names are specific for every host
	/// (they contain a host name as a prefix (like 'MYHOST\myrolename')) a special mechanism is used to map 
	/// those roles into the System relevant ones. This is called a <see cref="IRoleMapper"/>. 
	/// Basically it is a dictionary that maps an interlan role name (a dictionary key) 
	/// to its external equivalent that is unique for every host installation. Note that IRoleMapper 
	/// is a MANDATORY collaborator and must be installed for proper system usage.
	/// 
	/// To authenticate a principal the Authenticator uses native windows calls <c>LogonUser</c> and
	/// <c>CloseHandle</c>. The last error code is written in a System log and can be analysed afterwards
	/// in case of authentication mulfunction. To decode the last error code, please use MSDN or appropriate 
	/// C header file.
	/// </summary>
	/// <remarks>
	/// Since the Authenticator uses the native Win32API method calls, the code has 
	/// to have appropriate permissions to do this (which is normally a case in case of 
	/// an ASP.Net default application installation). However, as the <c>LoginUser</c> in 
	/// Windows versions up to Windows 2000 considered to be a privileged function only those processes
	/// that can substitute their current tokes with the LOCAL SYSTEM account can call this group of functions.
	/// (which effectively means that they are able to run as a LOCAL SYSTEM, which is 
	/// the highest privilegy a Windows process can have) 
	/// The latter is by no means the case of the default ASP.Net and IIS installations, 
	/// thus the usage of the fuction is not possible.
	/// To overcome this obstacle two ways are possible. First, one can raise the permission level 
	/// of the IIS and ASP.Net process so that they can act as a LOCAL SYSTEM 
	/// (this is normally done via Local Security Policy settings, please note that you have 
	/// to exit the policy setup program after making adjustments, 
	/// or else the changes will not be persisted and you will not see the updated values).
	/// However, the best approach is to use Windows 2003 (and may be higher versions, 
	/// though it is claimed that Windows XP has solved this problem either). Windows 2003 uses different 
	/// authentication mechanism and the aforementioned <c>LoginUser</c> function 
	/// requires no more privileged access. Thus, there will be no necessity to greek with the priviliges
	/// and the System Windows based security features can be used out of the box.
	/// </remarks>
    public class WindowsPrincipalAuthenticator : AbstractAuthenticator
	{
		ILog log = LogManager.GetLogger(typeof(WindowsPrincipalAuthenticator));

		private IRoleMapper roleMapper;

		public WindowsPrincipalAuthenticator()
		{
		}

		public IRoleMapper RoleMapper
		{
			set { roleMapper = value; }
		}

		public override IPrincipal Authenticate(string userName, string credentials)
		{
		    IntPtr pToken;
			bool res = LogonUser(userName, 
				null, 
				credentials, 
				3/*LOGON32_LOGON_NETWORK*/, 
				0/*LOGON_PROVIDER_DEFAULT*/, 
				out pToken);
			if(!res)
			{
				int errorCode = Marshal.GetLastWin32Error();
				log.Error("LastError=" + errorCode);
				try{CloseHandle(pToken);}catch{}
				return null;
			}

			WindowsIdentity identity = new WindowsIdentity(pToken, "NTLM",WindowsAccountType.Normal, true);
			try{CloseHandle(pToken);}catch{}
			IPrincipal principal = ConstructPrincipal(new WindowsPrincipal(identity));
//			Thread.CurrentPrincipal = principal;
			return principal;
		}

        public virtual void SignOut(IPrincipal principal)
		{
		}

	    [DllImport("Advapi32.dll", EntryPoint="LogonUser", CharSet=CharSet.Unicode, SetLastError=true)]
		private extern static bool LogonUser(string userName, 
			string domain, 
			string password, 
			uint logonType, 
			uint logonProvider, out IntPtr pToken);

		[DllImport("Kernel32.dll")]
		private extern static bool CloseHandle(IntPtr handle);

		private IPrincipal ConstructPrincipal(IPrincipal principal)
		{
			string[] roles = roleMapper.MapPrincipalToRoles(principal);
			GenericIdentity innerIdentity = new GenericIdentity(principal.Identity.Name, "Sedna");
			return new GenericPrincipal(innerIdentity, roles);
		}
	}
}
